/*
 * Copyright (c) 2023-2023 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.spring.common.filter.applet;

import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.elsfs.cloud.api.security.entity.SecurityUserEntity;
import org.elsfs.cloud.api.security.service.SecurityUserService;
import org.elsfs.cloud.spring.common.core.userdetails.SecurityUser;
import org.elsfs.cloud.spring.common.federation.FederatedIdentity;
import org.elsfs.cloud.spring.common.federation.FederatedIdentityService;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

/**
 * 根据 code 换取登录信息
 *
 * @author zeng
 */
@RequiredArgsConstructor
public class AppletAuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
  private final FederatedIdentityService federatedIdentityService;
  private final SecurityUserService securityUserService;

  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    var codeAuthenticationToken = (AppletAuthorizationCodeAuthenticationToken) authentication;
    var authorizationRequest = codeAuthenticationToken.getAppletAuthorizationRequest();
    ResponseBody responseBody = authorizationRequest.requestOpenid();
    if (responseBody.getOpenid() == null) {
      throw new AuthenticationServiceException(
          "errcode:" + responseBody.getErrcode() + "errmsg:" + responseBody.getErrmsg());
    }
    var federatedIdentity =
        federatedIdentityService.loadByIdentifierAndIdentityType(
            responseBody.getOpenid(), authorizationRequest.getType().getName());
    if (federatedIdentity != null) {
      var user = securityUserService.loadByUserId(federatedIdentity.getUserId());
      return UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities());
    }
    federatedIdentity = new FederatedIdentity();
    federatedIdentity.setIdentifier(responseBody.getOpenid());
    federatedIdentity.setAuthorizedClientRegistrationId(authorizationRequest.getAppid());
    federatedIdentity.setIdentityType(authorizationRequest.getType().getName());
    SecurityUserEntity user = requestUser(authorizationRequest, federatedIdentity);
    federatedIdentity.setUserId(user.getUserId());
    federatedIdentityService.save(federatedIdentity);
    return UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities());
  }

  private SecurityUserEntity requestUser(
      AppletAuthorizationRequest authorizationRequest, FederatedIdentity federatedIdentity) {
    String phone = authorizationRequest.requestPhone();
    if (phone != null) {
      try {
        return securityUserService.loadUserByPhone(phone);
      } catch (UsernameNotFoundException ignored) {
        // ignored
      }
    }
    return createUser(authorizationRequest, federatedIdentity, phone);
  }

  protected SecurityUser createUser(
      AppletAuthorizationRequest authorizationRequest,
      FederatedIdentity federatedIdentity,
      String phone) {
    SecurityUser user = new SecurityUser();
    user.setNickname("小程序用户");
    user.setPhone(phone);
    user.setUsername(federatedIdentity.getIdentifier());
    user.setPassword(UUID.randomUUID().toString());
    securityUserService.createUser(user);
    return user;
  }

  @Override
  public boolean supports(Class<?> authentication) {
    return AppletAuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);
  }
}
